/**
 * Interface.java
 *
 * @author Hagen Boehm
 */


package SSF.OS.OSPFv2;


import java.util.*;
import com.renesys.raceway.DML.*;
import SSF.Net.Util.IP_s;
import SSF.Net.NIC;
import SSF.Net.Net;
import SSF.Net.ptpLinkLayer;
import SSF.Net.lanLinkLayer;
import SSF.OS.BGP4.Util.AS_descriptor;
import SSF.OS.BGP4.Util.StringManip;
import SSF.OS.ProtocolException;
import SSF.OS.IpHeader;
import SSF.OS.ProtocolGraph;
import SSF.OS.Timer;


// ======================= class SSF.OS.OSPFv2.Interface ===================== //
/**
 * An OSPF interface.
 */
public class Interface implements Configurable {

  // ............................ constants ................................ //

  /** Indicates that the Interface is down. */
  public static final int DOWN         = 1;
  /** Indicates that the Interface is looped back. */
  public static final int LOOPBACK     = 2;
  /** Indicates that the Interface is in point-to-point state. */
  public static final int POINT2POINT  = 3;
  /** Indicates that the Interface is waiting for a Hello with DR and DBR identification. */
  public static final int WAITING      = 4;
  /** Indicates that the Interface is in election phase. */
  public static final int ELECTION     = 5;
  /** Indicates that the Interface is the Designated Router. */
  public static final int DR           = 6;
  /** Indicates that the Interface is the Backup Designated Router. */
  public static final int BDR          = 7;
  /** Indicates that the Interface is nor DR neither BDR. */
  public static final int DROTHER      = 8;

  /** Indicates that the link type the interface is connected to is point-to-point. */
  public static final byte PTP       = 0;
  /** Indicates that the link type the interface is connected to is broadcast. */
  public static final byte BROADCAST = 1;
  /** Indicates that the link type the interface is connected to is non broadcast multi access. */
  public static final byte NBMA      = 2;
  /** Indicates that the link type the interface is connected to is point-to-multipoint. */
  public static final byte PTM       = 3;
  /** Indicates that the link type the interface is connected to is virtual-link. */
  public static final byte VL        = 4;

  // ............................ member data .............................. //

  /** The Interface id. Used to determine the "real" Interface associated with this one.*/
  public Integer id; 
  
  /** The network type the interface is connected to (ptp, broadcast, nbma, ptm). */
  public byte networkType;

  /** The ip address of the interface. */
  public int ipaddr;
 
  /** The ip mask of the Interface. */
  public int ipmask;

  /** The actual state of the Interface (down, loopback, waiting,...) */
  public int state;

  /** A reference to the nh of the OSPF session above */
  public String rtrnh;
  /** A reference to the as_nh of the OSPF session above */
  public String as_nh;

  /** cost of this interface */
  public int cost;

  /** The Designated Router for the network to which the interface is attached. */
  public int desigRouter;

  /** The Backup Designated Router for the network to which the interface is attached. */
  public int backupDesigRouter;
  
  /** A list of neighbor data structures containing information about the relationship
   *  to the neighbors. */
  public HashMap neighbors;

  /** Priority of the router the interface is attached to.*/
  public int routerPrio;

  /** A link to the OSPF ProtocolSession this Interface belongs to. */
  public OSPF myOSPF;

  /** A link to the Area Data Structure this Interface belongs to. Can change when
   *  the Area is reconfigured.
   */
  public AreaData myArea;

  /** The content of an LS Ack Packet. This is used to accumulate several LSA Headers
   *  to be acknowledged in the next delAckTime seconds.
   */
  public LS_AckPacket lsAckContent;

  /** boolean value indicating that the Interface configuration has changed. */
  private boolean configChanged = false;

  /** A list of LSAs that must be flooded out this interface, but are not allowed to 
   *  send out immediately due to the PacingTimer. It is implemented as HashMap because
   *  it should be easy to replace old instances of LSAs by new ones.
   */ 
  public HashMap pacingLSAList;

  // - - - - - - - - - - - - - - - Time Variables - - - - - - - - - - - - - - - - - - //

  /** The seconds by which LSAs exiting the interface will have their age incremented. */
  public int transDelay;
  
  /** The period, in seconds, between transmissions of Hello packets on the interface. */
  public int helloInterval;

  /** The period, in seconds, that the router will wait to hear a Hello from a neighbor
   *  on the network to which the interface is connected before declaring the neighbor down.
   */
  public int routerDeadInterval;

  /** The period, in seconds, the router will wait between retransmissions of OSPF-Packets
   *  that have not been acknowledged.
   */
  public long rxmtInterval;

  /** The interval in seconds the router will wait before it sends an delayed Acknowledgment
   *  to all Neighbors of this Interface. This value should be less than rxmtInterval!
   */
  public long delAckTime;

  // - - - - - - - - - - - - - - - Interface Timers - - - - - - - - - - - - - - - - - //

  /** A Wait Timer which will wait for routerDeadInterval seconds before either beginning
   *  an election of DR and BDR, or receiving an incoming Hello Packet which advertises
   *  DR and BDR. */
  public BeforeElectionTimer waitTimer;

  /** A timer that is set to helloInterval. When it expires, a Hello Packet is transmitted
   *  from this Interface.
   */
  public SendNewHelloTimer helloTimer;

  /** A timer that delays sending of Acknowledgment Packets. When it expires, it send an
   *  Acknowledgment Packet with all LSA Headers filled in during its run time to all
   *  Neighbors of this Interface.
   */
  public DelayedAckTimer ackTimer;

  /** A timer that controls the rate at which LS Update packets are transmitted out this
   *  interface. It guaratees that not every single LSA is sent out in a separate LS Update 
   *  packet.
   */ 
  public PacingTimer pacingTimer;

  // ------------------------------ Interface.config ------------------------------------ //
  /**
   * Configure the OSPF interface parameters at the start and during the run by configUpdateMessage. 
   * Supported DML attributes are:
   * <PRE>
   * if [
   *      # The interface's ID (equal to the id of the according NIC)
   *      # mandatory!
   *      id      %I
   *      
   *      # This can be used to configure multiple interfaces at once
   *      idrange [from %I1! to %I1!] # idrange not impl. yet!!!
   *
   *      # The link type of the network connected to this interface
   *      # (possible values: "ptp", "broadcast", "nbma", "ptm", "vlink")
   *      # all values but "ptp" not supported!!!
   *      # mandatory!
   *      network             %S
   *
   *      # The interface output cost.
   *      # optional! default value: 1
   *      cost                %I
   *            
   *      # The router priority.
   *      # optional! default value: 0
   *      priority            %I
   *         
   *      # InfTransDelay
   *      # optional! default value: 1
   *      transmit-delay      %I
   *            
   *      # HelloInterval
   *      # optional! default value: 10
   *      hello-interval      %I
   *            
   *      # RouterDeadInterval
   *      # optional! default value: 40
   *      dead-interval       %I
   *            
   *      # RxmtInterval
   *      # optional! default value: 5
   *      retransmit-interval %I
   *    ]
   * </PRE>
   *
   * @param cfg contains the values for configurable ospf interface parameters
   * @exception configException  if any of the calls to <code>findSingle</code> 
   *                             throws such an exception.
   */

  public void config(Configuration cfg) throws configException {

    HashMap dynConfKeys = new HashMap();
    boolean isStub;

    for(Enumeration k = ((dmlConfig)cfg).keys();k.hasMoreElements();) {
      String ak = (String)k.nextElement();
      dynConfKeys.put(ak, new Integer(1));
    }

    rtrnh = myOSPF.nh;
    as_nh = myOSPF.as_nh;
    if (dynConfKeys.containsKey("cost")) {
      cost = (new Integer((String)cfg.findSingle("cost"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("priority")) {
      routerPrio = (new Integer((String)cfg.findSingle("priority"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("transmit-delay")) {
      transDelay = (new Integer((String)cfg.findSingle("transmit-delay"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("hello-interval")) {
      helloInterval = (new Integer((String)cfg.findSingle("hello-interval"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("dead-interval")) {
      routerDeadInterval = (new Integer((String)cfg.findSingle("dead-interval"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("retransmit-interval")) {
      rxmtInterval = (new Long((String)cfg.findSingle("retransmit-interval"))).longValue();
      rxmtInterval *= SSF.Net.Net.seconds(1.0);
      configChanged = true;
    }
  }

  // -------------------------- constructor Interface -------------------------- //

  /**
   * Constructs a new Interface.
   */

  public Interface(OSPF myOSPF, AreaData myArea, Configuration cfg) {
    this.myOSPF = myOSPF;
    this.myArea = myArea;
    state = DOWN;
    neighbors   = new HashMap();
    pacingTimer = new PacingTimer(myOSPF, myOSPF.pacingFloodTime);
    pacingTimer.cancel();
    pacingLSAList = new HashMap();

    try {
      // default values for Interface data structures
      id                 = new Integer((String)cfg.findSingle("id"));
      networkType        = getNetworkType((String)cfg.findSingle("network"));
      cost               = 1;
      routerPrio         = 0;
      transDelay         = 1;
      helloInterval      = 10;
      routerDeadInterval = 40;
      rxmtInterval       = 5 * SSF.Net.Net.seconds(1.0);
      delAckTime         = 1 * SSF.Net.Net.seconds(1.0);
      configChanged      = true;
      config(cfg);
    } catch (configException cex) {
      myOSPF.printConfigError("Interface", IP_s.IPtoString(ipaddr), cex);
    }
  }

  // ------------------------------- Interface.configRest -------------------------------- //
  /**
   * Additional configuration to the interface. Configure IP address, IP mask and the ospf 
   * neighbors and check the configured network type.
   * 
   * @param peers Enumeration of NIC's which are peers to this interface.
   * @exception configException thrown if configured linkType does not match the link layer
   *                            found in the corresponding NIC.
   */
  public void configRest(Enumeration peers) throws configException {
    Integer peerIPAddr;
    int shift;
    NIC localInterface = (NIC)myOSPF.rtr.interfaceNumbers.get(id);
    shift = 32 - localInterface.maskBits;
    if (shift == 32) ipmask = 0;
    else ipmask = -1 << shift;
    ipaddr = localInterface.ipAddr;
    
    if (peers == null) {
      return; // BJ, it's a virtual interface, has no peers
    }
    // check configured network type and initialize neighbors 
    // cause there is no broadcast or multicast
    switch (networkType) {
    case PTP : if (!(localInterface.link_hw instanceof ptpLinkLayer)) 
                 throw new configException("configured network mismatches local link type!");
               peerIPAddr = new Integer(((NIC)peers.nextElement()).ipAddr);
               neighbors.put(peerIPAddr, new Neighbor(myOSPF, myArea, this, peerIPAddr.intValue()));
    break;
    case BROADCAST : if (!(localInterface.link_hw instanceof lanLinkLayer)) 
                       throw new configException("configured network mismatches local link type!");
    break;
    default: 
             throw new configException("configured network is not implemented yet!");
    }

    // print Interface configuration if necessary.
    printConfiguration();
  }

  // ---------------------------------- Interface.init ----------------------------------- //
  /**
   * Start the ospf session on this interface (just call method "interfaceUp()"). Also used
   * when restarting ospf.
   */
  public void init() {
    // start OSPF on this interface
    interfaceUp();
  }

  // --------------------------------- Interface.reset ---------------------------------- //
  /**
   *  Reset the Interface Data Structure. That means stop all Timers and reset all Neighbor
   *  Data Structures. This is part of the stop process of the OSPF Session.
   */
  public void reset() {
    if (waitTimer != null) {
      waitTimer.cancel();
      waitTimer = null;
    }
    if (helloTimer != null) {
      helloTimer.cancel();
      helloTimer = null;
    }
    if (ackTimer != null) {
      ackTimer.cancel();
      ackTimer = null;
    }
    for (Iterator nghbrs = neighbors.values().iterator(); nghbrs.hasNext(); ) {
      ((Neighbor)nghbrs.next()).reset();
    }
  }

  // ---------------------------------- Interface.setArea ------------------------------- //
  /**
   *  When this Interface changes its Area, this method should be called to set the links
   *  to the Area Data Structure to the new Area.
   */
  public void setArea(AreaData newArea) {
    myArea = newArea;
    for (Iterator nbValues = (neighbors.values()).iterator(); nbValues.hasNext(); ) {
      ((Neighbor)nbValues.next()).myArea = newArea;
    }
  }

  // ------------------------------- Interface.receivedHello ---------------------------- //
  /**
   * When the Area Data Structure above gets a Hello Packet, it will call this method of the ospf
   * interface which is associated with the NIC on which the packet has come in. helloReceived
   * then further process the packet.
   */
  public void receivedHello(Packet helloPkt, int sourceIP) {
    HelloPacket helloContent = (HelloPacket)helloPkt.payload();
    // Do a generic Hello processing.
    // Check the Net Mask received with the Hello Packet!
    if (networkType != PTP) {
      if (ipmask != helloContent.netMask) {
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
	  myOSPF.printMessage("Network Mask mismatch detected! On " + IP_s.IPtoString(ipaddr) + 
                              " From OSPF Router " + helloPkt.nh + " Interface " +
                              IP_s.IPtoString(sourceIP) + " Packet discarded!");
        }
        return;
      }
    }
    // check the Hello Interval received with the Hello Packet!
    if (helloInterval != helloContent.helloInterval) {
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
        myOSPF.printMessage("Hello Interval mismatch detected! On " + IP_s.IPtoString(ipaddr) + 
                            " From OSPF Router " + helloPkt.nh + " Interface " + 
                            IP_s.IPtoString(sourceIP) + " Packet discarded!");
      }
      return;
    }
    // check the Router Dead Interval received with the Hello Packet!
    if (routerDeadInterval != helloContent.routerDeadInterval) {
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
        myOSPF.printMessage("Router Dead Interval mismatch detected! On " + IP_s.IPtoString(ipaddr) + 
                            " From OSPF Router " + helloPkt.nh + " Interface " + 
                            IP_s.IPtoString(sourceIP) + " Packet discarded!");
      }
      return;
    }
    // Check option bits received with the Hello Packet!
    if (helloContent.options[6] != myArea.options[6]) {
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
        myOSPF.printMessage("E-bit mismatch in option field detected! On " + IP_s.IPtoString(ipaddr) +
                            " From OSPF Router " + helloPkt.nh + " Interface " + 
                            IP_s.IPtoString(sourceIP) + " Packet discarded!");
      }
      return;
    }
    // now we come to a more specific processing depending on the Interface type.
    // Implementation of the Neighbor-State-Machine.
    // Special treatment for Point-to-Point links
    if (networkType == PTP) {
      // Unknown Neighbor seen. Delete the old one (because of PTP). Create a new one. 
      if (!neighbors.containsKey(new Integer(sourceIP))) {
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
          myOSPF.printMessage("Neighbor changed its Router ID! On " + IP_s.IPtoString(ipaddr) + 
                              " From OSPF Router " + helloPkt.nh + " Interface " + 
                              IP_s.IPtoString(sourceIP));
	}
        neighbors.clear();
        neighbors.put(new Integer(sourceIP), new Neighbor(myOSPF, myArea, this, sourceIP));
      }
      Neighbor myNeighbor = (Neighbor)neighbors.get(new Integer(sourceIP));
      // Neighbor seen who doesn't know me.
      if (!helloContent.neighborIDs.contains(new Integer(ipaddr)))
        myNeighbor.oneWayHelloRec(helloPkt, sourceIP);
      else 
      // Neighbor seen who knows me.
        myNeighbor.twoWayHelloRec(helloPkt, sourceIP);
    } else {
      // Special treatment for other than Point-to-Point links.
      // set Router Priority, DR and BDR
    } 
  }

  // -------------------------- Interface.receivedDDPacket ----------------------------- //
  /**
   * When the Area Data Structure, this Interface belongs to, gets a Database Description 
   * Packet, it will call this method to further process it. A decision is made here, 
   * which event should be triggered on the Neighbor State Machine.
   */
  public void receivedDDPacket(Packet dataDescPkt, Integer sourceIP) throws ProtocolException {
    Neighbor fromNeighbor;
    int receivedMTU;

    if (neighbors.containsKey(sourceIP)) {
      // Neighbor is known 
      fromNeighbor = (Neighbor)neighbors.get(sourceIP);
      receivedMTU = ((DD_Packet)dataDescPkt.payload()).interfaceMTU;
      if (receivedMTU > myOSPF.MTU) {
	// IP datagram size is larger than the router can accept
	// discard the packet.
        if (myOSPF.debugOptions[9] || myOSPF.debugOptions[3]) {
          myOSPF.printMessage("MTU too large! DD Packet rejected!");
        }
        return;
      }
      switch (fromNeighbor.state) {
      case Neighbor.DOWN:
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
          myOSPF.printMessage("Neighbor state is DOWN! DD Packet rejected!");
      break;
      case Neighbor.ATTEMPT:
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
          myOSPF.printMessage("Neighbor state is ATTEMPT! DD Packet rejected!");
      break;
      case Neighbor.INIT:
        fromNeighbor.twoWayReceived(dataDescPkt);
      break;
      case Neighbor.TWOWAY:
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
          myOSPF.printMessage("Neighbor state is TWOWAY! DD Packet ignored!");
      break;
      case Neighbor.EXSTART:
        fromNeighbor.negotiation(dataDescPkt);
      break;
      case Neighbor.EXCHANGE:
        fromNeighbor.exchange(dataDescPkt);
      break;
      case Neighbor.LOADING:
        fromNeighbor.subseqTreatment(dataDescPkt);
      break;
      case Neighbor.FULL:
        fromNeighbor.subseqTreatment(dataDescPkt);
      break;
      default:
        throw new ProtocolException("Received DD Packet with unknown type!");
      }  
    } else {
      // what should be done with DD Packets from unknown Neighbors???
      myOSPF.printError("Interface", IP_s.IPtoString(ipaddr), "Received DD Packet from unknown Neighbor!");
    }
  }

  // ----------------------- Interface.receivedLSRequest ---------------------------- //
  /** When the Area Data Structure of this Interface gets a Link State Request Packet
   *  it calls this method to further process the Packet.
   */
  public void receivedLSRequest(Packet lsReqPkt, Integer sourceIP) {
    Neighbor fromNeighbor;
    int state;
    if (neighbors.containsKey(sourceIP)) {
      // Neighbor is known 
      fromNeighbor = (Neighbor)neighbors.get(sourceIP);
      state = fromNeighbor.state;
      if ((state == Neighbor.EXCHANGE) || (state == Neighbor.LOADING) || (state == Neighbor.FULL))
	fromNeighbor.processLSRequest(lsReqPkt);
      else
       if (myOSPF.debugOptions[9] == true)
         myOSPF.printMessage("Neighbor state is " + fromNeighbor.getStrFromState(state) +
         "! LS Request Packet ignored!");
    } else {
      // what should be done with LS Request Packets from unknown Neighbors???
      myOSPF.printError("Interface", IP_s.IPtoString(ipaddr), "Received LS Request Packet from unknown Neighbor!");
    }
  }

  // ------------------------ Interface.receivedLSUpdate ---------------------------- //
  /** When the Area Data Structure, this Interface belongs to, gets a Link State Update
   *  Packet it calls this method to further process the Packet.
   */
  public void receivedLSUpdate(Packet lsUpdPkt, Integer sourceIP) {
    Neighbor fromNeighbor;
    if (neighbors.containsKey(sourceIP)) {
      // Neighbor is known
      fromNeighbor = (Neighbor)neighbors.get(sourceIP);
      switch(fromNeighbor.state) {
      case Neighbor.EXCHANGE:
      case Neighbor.LOADING:
	fromNeighbor.processLSUpdate(lsUpdPkt);
        myArea.databaseDebugInfo();
      break;
      case Neighbor.FULL:
	fromNeighbor.processFloodedUpdate(lsUpdPkt);
        myArea.databaseDebugInfo();
      break;
      default:
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true)
         myOSPF.printMessage("Neighbor state is " + fromNeighbor.getStrFromState(fromNeighbor.state) +
         "! LS Update Packet ignored!");
      }
    } else {
      // what should be done with LS Update Packets from unknown Neighbors???
      myOSPF.printError("Interface", IP_s.IPtoString(ipaddr), "Received LS Update Packet from unknown Neighbor!");
    }
  }

  // --------------------------- Interface.receivedLSAck ----------------------------------- //
  /** When the Area Data Structure, this Interface belongs to, gets a Link State Acknowledgment
   *  Packet it calls this method to further process the Packet.
   */
  public void receivedLSAck(Packet lsAckPkt, Integer sourceIP) {
    Neighbor fromNeighbor;
    if (neighbors.containsKey(sourceIP)) {
      // Neighbor is known
      fromNeighbor = (Neighbor)neighbors.get(sourceIP);
      if (fromNeighbor.state < Neighbor.EXCHANGE) {
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true)
         myOSPF.printMessage("Neighbor state is " + fromNeighbor.getStrFromState(fromNeighbor.state) +
         "! LS Ack Packet is discarded!");
      } else {
        fromNeighbor.processLSAck(lsAckPkt);
      }
    } else {
      // what should be done with LS Ack Packets from unknown Neighbors???
      myOSPF.printError("Interface", IP_s.IPtoString(ipaddr), "Received LS Ack Packet from unknown Neighbor!");
    }
  }

  // --------------------- Interface.clearLSAFromRetrLists ------------------------- //
  /** clears a particular LSA from every Neighbors link state retransmission list. If
   *  the List becomes therefore empty, stop the FloodingTimer of the Neighbor Data
   *  Structure.
   */
  public void clearLSAFromRetrLists(String key) {
    Neighbor nextNeighbor;
    for (Iterator nbValues = (neighbors.values()).iterator(); nbValues.hasNext(); ) {
      nextNeighbor = (Neighbor)nbValues.next();
      nextNeighbor.lsRetransList.remove(key);
    }
  }

  // ------------------------ Interface.delayedAcknowledge ----------------------------- //
  /** called if a new LSA should be delayed acknowledged. Build a new LS Ack Packet and set
   *  the ackTimer, or add the Header of ackLSA to an existing LS Ack Packet.
   */
  public void delayedAcknowledge(LSA ackLSA) {
    if ((ackTimer != null) && (!ackTimer.isCancelled())) {
      // There is already an LS Ack Packet waiting to be sent.
      // Just add the LSA to be acked to the Packet.
      lsAckContent.put(ackLSA);
    } else {
      // There is no LS Ack Packet waiting to be sent. 
      // Construct a new Timer to delay sending of an Acknowledgment.
      ackTimer = new DelayedAckTimer(myOSPF, delAckTime);
      // Construct a new LS Ack Packet...
      lsAckContent = new LS_AckPacket();
      // ...add the LSA to be acked to the Packet...
      lsAckContent.put(ackLSA);
      // ...and start the Timer.
      ackTimer.set(delAckTime);
    }
  }

  // -------------------------- Interface.getNetworkType ----------------------------------- //
  /** converts the value of the configuration key "network" in a standarized constant.
   *  @param key the value of configuration key "network"
   *  @return a byte constant representing a special network type
   */
  private byte getNetworkType(String key) {
    if (key.equals("ptp"))       return (byte)0;
    if (key.equals("broadcast")) return (byte)1;
    if (key.equals("nbma"))      return (byte)2;
    if (key.equals("ptm"))       return (byte)3;
    if (key.equals("vlink"))     return (byte)4;
    return (byte)-1;
  }

  // --------------------------- Interface.nTypeToString ----------------------------------- //
  /** converts the value of networkType to a String for debugging purposes.
   *  @param nType the value of networkType.
   *  @return the String interpretation of this value.
   */
  private String nTypeToString(byte nType) {
    if (nType == PTP)       return "point-to-point";
    if (nType == BROADCAST) return "broadcast";
    if (nType == NBMA)      return "non broadcast multi access";
    if (nType == PTM)       return "point-to-multipoint";
    if (nType == VL)        return "virtual-link";
    return "not defined!";
  }

  // - - - - - - - - - - - - - - Events on the State Machine - - - - - - - - - - - - - - - - //

  
  // ----------------------------- Interface.loopInd ----------------------------------- //
  /**
   * Used by Lower-level protocols and network management to indicate that the network 
   * interface is now looped back to itself.
   */
  public void loopInd() {
  //reset all interface variables. 
    state = LOOPBACK;
    neighbors.clear(); 
    
  }

  // ----------------------------- Interface.unloopInd -------------------------------- //
  /**
   * Used by Lower-level protocols and network management to indicate that the network interface is no  
   * longer looped back to itself.
   */
  public void unloopInd() {
    if (state == LOOPBACK)
     state = DOWN;
    
  }

  // ----------------------------- Interface.interfaceUp ------------------------------- //
  /**
   * Used by the Area Data Structure this Interface belongs to. It enables the Interface to
   * run OSPF. In particular, determine state of Interface, start the Hello Timer enabling 
   * periodic sending of Hello Packets.
   */
  public void interfaceUp() {
    NIC localInterface = (NIC)myOSPF.rtr.interfaceNumbers.get(id);
    
    if (localInterface.isVirtual) {
      state = LOOPBACK;
      return;
    }
    if (!localInterface.isUp()) {
      // The NIC associated with this Interface is down.
      state = DOWN;
      return;
    }
    if ((networkType == PTP) || (networkType == PTM) || (networkType == VL)) {
    /** In case of p2p, p2m network or virtual link actualState chages to  POINT2POINT */
      state = POINT2POINT;
    } else {
      if (((networkType == BROADCAST) || (networkType == NBMA)) && (routerPrio == 0))
      /** otherwise, if the router is not egligible to become DR the interface state transitions to
      /*  DROTHER.
       */
      state = DROTHER;
    }
    /** A random value indicating the time the helloTimer starts sending hellos. The value
     *  is in range of 0 to 10.
     */
    long startHelloTime =  (int)(myOSPF.randomStream.nextDouble() * 10.0) * SSF.Net.Net.seconds(1.0);
    helloTimer = new SendNewHelloTimer(myOSPF, startHelloTime);
    helloTimer.set(startHelloTime);
  }

  // ----------------------------- Interface.interfaceDown ----------------------------- //
  /**
   * Used by Lower-level protocols to indicate that the network interface is no longer
   * functional.
   */
  public void interfaceDown() {
    //reset all interface variables. 
    state = DOWN;
    helloTimer.cancel();
    ackTimer.cancel();
    pacingTimer.cancel();
    neighbors.clear();   
  }

  // -------------------------- Interface.killNeighbor -------------------------- //
  /**
   * Used to destroy a neighbor connection associated with the interface. The neighbor is identified
   * by the ip address of its interface.
   *
   * @param ipaddress contains the ip address of the neighbors interface.
   */
  public void killNeighbor(int ipaddress) {
    
    
  }

  // -------------------------- Interface.toString ------------------------------ //
  /** 
   * Converts the Interface to a string. You can also use the briefer format 
   * <code>toMinString</code>.
   */

  public String toString() {
    String str = "";
    str = "[Type: " + nTypeToString(networkType) + ", IP_addr: " + IP_s.IPtoString(ipaddr) +
          ", IP_mask: " + ipmask + ", State: " + state + ", ROUTER_NH: " + 
          rtrnh + ", AS: " + as_nh + ", Cost: " + cost + " Router Priority: " 
          + routerPrio + "\nTransmission-Delay: " + transDelay + "\nHello Interval: " 
          + helloInterval + "\nDead Interval: " + routerDeadInterval 
          + "\nRetransmission Interval: " + rxmtInterval;
    return str;
  }

  // --------------------------Interface.printConfiguration ------------------------- //
  /** print current configuration of the interface if necessary. That means if configuration
   *  has changed since last print and one of the following ospf options are set:
   *  "show_interfaces", "show_config", "show_all".
   */
  public void printConfiguration() {
    if ((myOSPF.debugOptions[9] == true || myOSPF.debugOptions[8] == true || myOSPF.debugOptions[7]) 
        && configChanged) {
      int mask = 32 - (int)(Math.log((new Double(ipmask)).doubleValue() * -1.0)/Math.log(2.0));
      myOSPF.printMessage("Configuration of Interface "+IP_s.IPtoString(ipaddr)+
        " changed!\ninterface type: " + nTypeToString(networkType) + "\nip mask: /" + mask + 
        "\narea id: " + myArea.areaID + "\nhello interval: " + helloInterval + 
        "\nrouter dead interval: " + routerDeadInterval + "\ninf trans delay: " + transDelay + 
        "\nrouter priority: " + routerPrio + "\ncost: " + cost + "\nrxmt interval: " + 
        rxmtInterval/SSF.Net.Net.seconds(1.0));
      configChanged = false;
    }
  }

  // ------------------------- Interface.toMinString(boolean) ------------------------ //
  /**
   * Converts the interface to a string using a briefer format than
   * <code>toString</code>.
   *
   * @param usenhi  Whether or not to use NHI addressing when possible.
   * @return the interface in minimal string format
   */
  public String toMinString(boolean usenhi) {
    String str = nTypeToString(networkType) + " ";

    if (usenhi) {
      String nhiaddr = OSPF.topnet.ip_to_nhi(IP_s.IPtoString(ipaddr));
      if (nhiaddr == null) {
        nhiaddr = "-";
      }
      str += StringManip.pad(nhiaddr, 8, ' ', true) + " ";

      if (nhiaddr == null) {
        nhiaddr = "-";
      }
      str += StringManip.pad(nhiaddr, 8, ' ', true) + " ";
    } else {
      str += StringManip.pad(IP_s.IPtoString(ipaddr), 12, ' ', true) + " ";
    }

    str += ipmask +"   "+ state +"  "+ StringManip.pad(rtrnh,6,' ',true) + " ";
    
    if (usenhi) {
      String as_string = as_nh;
      if (as_nh.equals("")) {
        as_string = "<null>";
      }
      str += StringManip.pad(as_string,6,' ',true) + " ";
    } else {
      str += StringManip.pad(""+AS_descriptor.nh2as(as_nh),6,' ',true) + " ";
    }
    return str;
  }

  // --------------------------- toMinString() --------------------------- //
  /**
   * Converts the interface to a string using a briefer format than
   * <code>toString</code>.
   *
   * @return the interface in minimal string format
   */
  public String toMinString() {
    return toMinString(false);
  }

  // ============================== inner class BeforeElectionTimer ================================ //
  /**
  /* The BeforeElectionTimer waits for an HelloPacket which tells the interface who is DR and BDR.
   * If there is no such a packet before expiring, the interface will go to election state and
   * elect DR and BDR. The length of the timer is RouterDeadInterval seconds.
   */
  
  public class BeforeElectionTimer extends SSF.OS.Timer {
    
    // -------------------------- constructor BeforeElectionTimer ----------------------------- //

    public BeforeElectionTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // -------------------------- BeforeElectionTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been cancelled. */

    public void callback() {
      // 

    }

  } // end of class BeforeElectionTimer


  // =============================== inner class SendNewHelloTimer ================================ //
  /**
  /* The Inactivity Timer for this Interface class. A single shot timer whose firing indicates 
   * that a new Hello Packet should be send out of this Interface.  The length of this timer is 
   * helloInterval seconds.
   */
  
  public class SendNewHelloTimer extends SSF.OS.Timer {
    
    // -------------------------- constructor SendNewHelloTimer ----------------------------- //

    public SendNewHelloTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // -------------------------- SendNewHelloTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been cancelled. Construct a new
     *  Hello Packet and send it to the point-to-point Neighbor. */

    public void callback() {
      IpHeader sendHello;
      Packet helloPkt;
      HelloPacket helloContent;
      Vector neighborIDs = new Vector();
      for (Iterator nbKeys = neighbors.keySet().iterator(); nbKeys.hasNext();) {
	Integer nextNbKey = (Integer)nbKeys.next();
        if (((Neighbor)neighbors.get(nextNbKey)).state > Neighbor.DOWN) {
          neighborIDs.addElement(nextNbKey);
        }
      }
      if (myOSPF.debugOptions[9] || myOSPF.debugOptions[2])
        myOSPF.printMessage(IP_s.IPtoString(ipaddr) + "||Send new Hello Packet!||");
      for (Iterator nbVals = neighbors.values().iterator(); nbVals.hasNext();) {
        Neighbor nextNeighbor = (Neighbor)nbVals.next();
        helloContent = new HelloPacket(helloInterval, myArea.options, routerDeadInterval, neighborIDs);
        helloPkt = new Packet(Packet.HELLO, myOSPF.rtrid, rtrnh, as_nh, myArea.areaID);
        helloPkt.carryPayload(helloContent);
        sendHello = new IpHeader(myOSPF.PROTOOSPF, ipaddr, nextNeighbor.neighborIPAddr, 1);
        sendHello.carryPayload(helloPkt);
        try {
          myOSPF.ip.push(sendHello, myOSPF);
        } catch (ProtocolException PExc) {
	    myOSPF.printError("Interface", IP_s.IPtoString(ipaddr), "Hello Packet could not be sent!");
        }
      }
      this.set((long)helloInterval * SSF.Net.Net.seconds(1.0));
    }
  } // end of class SendNewHelloTimer


  // ============================== inner class DelayedAckTimer ================================ //
  /**
   * The DelayedAckTimer is started if a new LS Ack Packet should be sent delayed. When it expires,
   * it sends lsAckContent to all Neighbors attached to this Interface. During the runtime of
   * this Timer lsAckContent can be filled with another LSA Headers to be acknowledged.
   */
  
  public class DelayedAckTimer extends SSF.OS.Timer {
    
    // ----------------------------- constructor DelayedAckTimer ------------------------------- //

    public DelayedAckTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // ------------------------------- DelayedAckTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been cancelled. */

    public void callback() {
      Neighbor nextNeighbor;
      for (Iterator nb = neighbors.values().iterator(); nb.hasNext(); ) {
	nextNeighbor = (Neighbor)nb.next();
        if (nextNeighbor.state >= Neighbor.EXCHANGE ) nextNeighbor.sendPacket(lsAckContent);
      }
      this.cancel();
    }

  } // end of class DelayedAckTimer

  // =============================== inner class PacingTimer =================================== //
  /**
   *  A timer used to pace LS Update Packets when first flooded out this interface. The LSAs are
   *  stored in the "Neighbor.pacingFloodList" before first sent out. Thereafter, they are 
   *  transferred to the "Neighbor.lsRetransList".
   */
  
  public class PacingTimer extends SSF.OS.Timer {
    
    // ----------------------------- constructor PacingTimer ------------------------------- //

    public PacingTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // ------------------------------- PacingTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been cancelled. */

    public void callback() {
      Neighbor nextNeighbor;
      boolean startAgain = false;
      this.cancel();
      for (Iterator nghbrs = neighbors.values().iterator(); nghbrs.hasNext(); ) {
	nextNeighbor = (Neighbor)nghbrs.next();
        if (nextNeighbor.startFlooding()) startAgain = true;
      }
      if (startAgain) this.set(myOSPF.pacingFloodTime);
    }

    // ------------------------------- PacingTimer.addLSA -------------------------------- //
    /** Determine the neighbors to which a new LSA must be flooded out and put it on the
     *  neighbors pacingFloodList. And set the timer if necessary. This method implements
     *  RFC 2328 chapter 13.3 (1).
     *  @param floodLSA An LSA that may be flooded out this interface.
     *  @param neighborID int value, identifying the neighbor from whom the LSA was received.
     */
    public void addLSA(LSA floodLSA, int neighborID) {
      Neighbor nextNeighbor;
      String key = floodLSA.getKey();
      for (Iterator nghbrs = neighbors.values().iterator(); nghbrs.hasNext(); ) {
	nextNeighbor = (Neighbor)nghbrs.next();
        if (nextNeighbor.state < Neighbor.EXCHANGE) continue;
        if ((nextNeighbor.state == Neighbor.EXCHANGE) || (nextNeighbor.state == Neighbor.LOADING)) {
          if (nextNeighbor.lsReqList.containsKey(key)) {
	    String compare = myArea.database.isNewerThan(floodLSA, (LSA)nextNeighbor.lsReqList.get(key));
            if (compare.equals("false")) continue;
            if (compare.equals("identical")) {
	      nextNeighbor.removeRequest(key);
              continue;
            }
            if (compare.equals("true")) nextNeighbor.removeRequest(key);
	  }
	}
	if (nextNeighbor.neighborID == neighborID) continue;
        nextNeighbor.pacingFloodList.put(key, floodLSA);
        if (this.isCancelled()) {
	  long timeDiff = myOSPF.inGraph().now() % myOSPF.pacingFloodTime;
	  if (timeDiff == 0) this.set(myOSPF.pacingFloodTime);
	  else this.set(timeDiff);
        }
      }
    }

  } // end of class PacingTimer


} // end of class Interface







